#ifndef MV_MULT_HPP
#define MV_MULT_HPP

#include <iostream>
#include <sys/time.h>
#include <omp.h>

typedef struct timeval timeVal;
using namespace std;

/* calcRuntime() */
unsigned long calcRuntime(timeVal start, timeVal end)
{
	/* calc time difference */
	timeVal diff;
	diff.tv_sec = end.tv_sec - start.tv_sec;
	if (start.tv_usec > end.tv_usec)
	{
		diff.tv_usec = (1e6 - end.tv_usec) + start.tv_usec;
	}
	else
	{
		diff.tv_usec = end.tv_usec - start.tv_usec;
	}
	
	return diff.tv_sec * 1e6 + diff.tv_usec;
}

/** class Vector*/
template <class T>
class Vector{
private:
	T* mVector;

	unsigned mSize;
public:
	Vector();
	Vector(unsigned size);
	Vector(const Vector<T>& cpy);
	~Vector();

	void init(void);

	unsigned getSize();

	T& operator[](int i);
	Vector<T>& operator=(const Vector<T>& vect);

	template <class AD>
	friend ostream& operator<<(ostream& stream, Vector<AD> &v);
	
};/* end of class */

/* class Matrix */
template <class T>
class Matrix{
private:
	T* mMatrix;

	unsigned mSize;
	unsigned mThreads;
	void init(void);
public:
	Matrix(unsigned size);
	Matrix(unsigned size,unsigned threads);
	~Matrix();

	Vector<T> operator*(Vector<T>& vector);

	template <class AD>
	friend ostream& operator<<(ostream& stream, Matrix<AD> &m);
};/* end of class*/

/***********************************************************************************/
/* Vector<T>::init */
template <class T>
void Vector<T>::init(void){
	for (unsigned i = 0; i < this->mSize; i++){
		*(mVector + i) = (T)(rand()%1000) / (T)10.0;
	}
}

/* Vector<T>::getSize */
template <class T>
unsigned Vector<T>::getSize(){
	return this->mSize;
}

/* Vector<T>::Vector(default) */
template <class T>
Vector<T>::Vector(){
	this->mSize = 5;
	this->mVector = new T[mSize];

	for (unsigned i = 0; i < this->mSize; i++){
		*(mVector + i) = 0;
	}
}

/* Vector<T>::Vector */
template <class T>
Vector<T>::Vector(unsigned size){
	this->mSize = size;
	this->mVector = new T[mSize];

	for (unsigned i = 0; i < this->mSize; i++){
		*(mVector + i) = 0;
	}
}

/* Vector<T>::Vector(copy) */
template <class T>
Vector<T>::Vector(const Vector<T>& cpy){
	mSize = cpy.mSize;
	mVector = new T[mSize];

	for (unsigned i = 0; i < this->mSize; i++){
		mVector[i] = cpy.mVector[i];
	}
}

/* Vector<T>::Vector(Destructor) */
template <class T>
Vector<T>::~Vector(){
	if (this->mVector != NULL){
		delete[]  mVector;
		mVector = NULL;
	}
}

/* Vector<T>::operator[] */
template <class T>
T& Vector<T>::operator[](int i){
	return this->mVector[i];
}

/* Vector<T>::operator= */
template <class T>
Vector<T>& Vector<T>::operator=(const Vector<T>& vect){
	if (this != &vect){
		delete[] mVector;
		mSize = vect.mSize;
		mVector = new T[mSize];
		for (unsigned i = 0; i < mSize; i++){
			mVector[i] = vect.mVector[i];
		}
	}

	return *this;
}

/***********************************************************************************/

/* Matrix<T>::init */
template<class T>
void Matrix<T>::init(void){
	for (unsigned j = 0; j < mSize; j++){
		for (unsigned i = 0; i < mSize; i++){
			*(mMatrix + i + j*mSize) = (T)(rand()%1000) / (T)10.0;
		}
	}
}

/* Matrix<T>::Matrix */
template <class T>
Matrix<T>::Matrix(unsigned size):mSize(size),mThreads(1){
	this->mMatrix = new T[mSize*mSize];

	init();
}

/* Matrix<T>::Matrix */
template <class T>
Matrix<T>::Matrix(unsigned size, unsigned threads):mSize(size),mThreads(threads){
	this->mMatrix = new T[mSize*mSize];

	init();
}

/* Matrix<T>::~Matrix(Destructor) */
template <class T>
Matrix<T>::~Matrix(){
	if (this->mMatrix != NULL){
		delete[] mMatrix;
	}
}

/* Matrix<T>::operator* */
template <class T>
Vector<T> Matrix<T>::operator*(Vector<T>& vector){
	timeVal start,end;
	Vector<T> erg(vector.getSize());
	unsigned i, j = 0;
	unsigned long runtime = 0;	 
	
	
/*Do parallel work*/
	gettimeofday(&start,NULL);
#pragma omp parallel num_threads(this->mThreads)
	{
#pragma omp for
		for (j = 0; j < this->mSize; j++){
			for (i = 0; i < vector.getSize(); i++){
				erg[j] += *(mMatrix+i+j*vector.getSize()) * vector[i];
			}
		}
	}

	gettimeofday(&end,NULL);
	
	runtime = calcRuntime(start,end);
	cout << "Runtime: " << runtime << " [us] " << runtime/1e6 << " [sec] Size: " << mSize << " Threads: " << mThreads << endl;

	return erg;
}

/***********************************************************************************/

/**** ostream Vector<T> ****/
template <class AD>
ostream& operator<<(ostream& stream, Vector<AD> &v){
	for (int i = 0; i < v.getSize(); i++){
		stream << v[i] << " ";
	}
	stream << endl;
	return stream;
}

/**** ostream Matrix<T> ****/
template <class AD>
ostream& operator<<(ostream& stream, Matrix<AD> &m){
	for (int j = 0; j < m.mSize; j++){
		for (int i = 0; i < m.mSize; i++){
			stream << *(m.mMatrix+j*m.mSize+i) << " ";
		}
		stream << endl;
	}
	return stream;
}

#endif
